package org.yinxianren.www.study_02.study.netty.one;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.ByteToMessageDecoder;

import java.util.List;


/**
 * Netty 客户端的通信步骤大致为：
 *
 *      创建一个 NIO 线程组，用于处理服务器与客户端的连接，客户端不需要用到 boss worker。
 *      创建一个 Bootstrap 对象，配置 Netty 的一系列参数，由于客户端 SocketChannel 没有父亲，所以不需要使用 childoption。
 *      创建一个用于实际处理数据的类ChannelInitializer，进行初始化的准备工作，比如设置接受传出数据的字符集、格式以及实际处理数据的接口。
 *      配置服务器 IP 和端口号，建立与服务器的连接。
 *
 *
 *
 *
 * Future
 *
 * 　　它提供了另外一种通知应用操作已经完成的方式。每个 Netty 的出站(outbound) I/O 操作都会返回一个 ChannelFuture ，
 *    它允许一个或者多个 ChannelFutureListener 实例，回调方法 operationComplete() 会在操作完成时调用，下面为示例代码：
 *   // 启动客户端，客户端用connect连接
 * ChannelFuture f = b.connect(host, port).sync();
 * f.addListener(new ChannelFutureListener() {
 *     public void operationComplete(ChannelFuture future) throws Exception {
 *         if(future.isSuccess()) {        // 连接成功
 *             ByteBuf buf = Unpooled.copiedBuffer("hello",             // 写数据
 *                     Charset.defaultCharset());
 *             ChannelFuture wf = future.channel().writeAndFlush(buf);    // 发送数据
 *         } else {
 *             // 打印错误
 *             Throwable cause = future.cause();
 *             cause.printStackTrace();
 *         }
 *     }
 * });
 *
 * 上面的代码首先连接到远程地址，当连接完成时，检查连接状态，若成功发送 hello，否则抛出错误。
 *
 */
public class TimeClientPOJO {

    public static void main(String[] args) throws Exception{
        String host = "127.0.0.1";            // ip
        int port = 8080;                    // 端口
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            Bootstrap b = new Bootstrap();            // 与ServerBootstrap类似
            b.group(workerGroup);                    // 客户端不需要boss worker
            b.channel(NioSocketChannel.class);
            b.option(ChannelOption.SO_KEEPALIVE, true);    // 客户端的socketChannel没有父亲
            b.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    // bean
                    ch.pipeline().addLast(new TimeDecoderPOJO() ,new TimeClientHandlerPOJO());
                }
            });

            // 启动客户端，客户端用connect连接
            ChannelFuture f = b.connect(host, port).sync();

            // 等待连接关闭
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
        }
    }
}



//自定义客户端业务逻辑类
class TimeClientHandlerPOJO extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 直接将信息转换成Time类型输出即可
        Time time = (Time)msg;
        System.out.println("TimeClientHandlerPOJO===>"+time);
        ctx.close();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}


//自定义客户端数据解码类
class TimeDecoderPOJO extends ByteToMessageDecoder {
    /**
     * 有新数据接收时调用
     * 为防止分包现象，先将数据存入内部缓存，到达满足条件之后再进行解码
     */

    // Netty 中使用 ByteBuf 代替 Java NIO 提供的 ByteBuffer 作为字节的容器
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        System.out.println("#################自定义客户端数据解码类################");
        if(in.readableBytes() < 4) {
            return;
        }

        // out添加对象则表示解码成功
        out.add(new Time(in.readUnsignedInt()));
    }
}